package com.meeruu.sg.account.core.domain.biz;

import com.meeruu.common.utils.LoggerUtil;
import com.meeruu.sg.account.core.api.constant.AccountGroup;
import com.meeruu.sg.account.core.api.constant.FundsFlowDirect;
import com.meeruu.sg.account.core.api.constant.FundsOprType;
import com.meeruu.sg.account.core.api.dto.request.fund.ChargeRequest;
import com.meeruu.sg.account.core.api.dto.request.fund.FundDelayInTransRequest;
import com.meeruu.sg.account.core.api.dto.request.fund.FundTransRequest;
import com.meeruu.sg.account.core.api.dto.response.AccountResponseDTO;
import com.meeruu.sg.account.core.api.exception.AccountException;
import com.meeruu.sg.account.core.api.exception.ErrorCode;
import com.meeruu.sg.account.core.domain.aspect.RequestSink;
import com.meeruu.sg.account.core.domain.vo.account.AccountSortVo;
import com.meeruu.sg.account.core.domain.vo.account.BaseAccount;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;

/**
 * @author yangqintao
 * @date 2019-05-17
 * @description 充值、转账业务
 */
@Slf4j
@Service
public class FundsTransferBiz {


    @Resource
    private TransactionTemplate transactionTemplate;

    @Autowired
    private FundsBalanceBiz fundsBalanceBiz;

    @Autowired
    private AccountQueryBiz accountQueryBiz;


    /**
     * 充值
     *
     * @param request
     */
    @RequestSink
    public String charge(ChargeRequest request) {
        return transactionTemplate.execute((TransactionStatus status) -> {

            String accountId = request.getAccountId();
            AccountGroup accountGroup = request.getAccountGroup();

            Map<String, BaseAccount> accountId2Account = fundsBalanceBiz.getAccountBySortForUpdate(AccountSortVo.builder()
                    .accountId(accountId)
                    .accountGroup(accountGroup)
                    .build());
            BaseAccount account = accountId2Account.get(accountId);
            // 校验账户状态
            fundsBalanceBiz.checkStatus(account.getStatus(), FundsFlowDirect.IN);

            BigDecimal amount = request.getChargeAmount();
            Date applyDate = request.getApplyDate();

            // 资金流水添加
            String flowId = fundsBalanceBiz.addFundsFlow(accountId, null, amount, FundsFlowDirect.IN,
                    FundsOprType.RECHARGE, applyDate, account.getAccountType());

            // 起始余额
            BigDecimal beginBalance = account.getAvailableBalance();

            // 余额+ 总金额+
            account.setAvailableBalance(beginBalance.add(amount));
            account.setTotalBalance(account.getTotalBalance().add(amount));

            // 更新用户金额
            fundsBalanceBiz.updateBalance(account, accountGroup, beginBalance);

            return flowId;
        });

    }


    /**
     * 转账接口
     *
     * @param request
     */
    @RequestSink
    public String applyTransfer(FundTransRequest request) {

        return transactionTemplate.execute((TransactionStatus status) -> {

            BigDecimal amount = request.getTransAmount();
            FundsOprType flowType = FundsOprType.TRANSFER;
            Date applyDate = request.getApplyDate();

            String outId = request.getOriginAccountId();
            AccountGroup outAccountGroup = request.getOriginAccountGroup();

            String inId = request.getTargetAccountId();
            AccountGroup inAccountGroup = request.getTargetAccountGroup();

            AccountSortVo inAccountSortVo = AccountSortVo.getInstance(inId, inAccountGroup);
            AccountSortVo outAccountSortVo = AccountSortVo.getInstance(outId, outAccountGroup);

            // 按字典顺序获取账户信息（悲观锁）
            Map<String, BaseAccount> id2BaseAccount = fundsBalanceBiz.getAccountBySortForUpdate(inAccountSortVo, outAccountSortVo);

            // 出账户资金-
            BaseAccount outAccount = id2BaseAccount.get(outId);
            outAccount = updateOutAccountBalance(amount, outAccount, outAccountGroup);

            // 进账户资金+
            BaseAccount inAccount = id2BaseAccount.get(inId);
            inAccount = updateInAccountBalance(amount, inAccount, inAccountGroup);

            // 流水添加
            String flowId = fundsBalanceBiz.addFundsFlow(outId, inId, amount, FundsFlowDirect.OUT,
                    flowType, applyDate, outAccount.getAccountType());
            fundsBalanceBiz.addFundsFlow(inId, outId, amount, FundsFlowDirect.IN,
                    flowType, applyDate, inAccount.getAccountType());

            return flowId;


        });


    }


    /**
     * 转账:入账户先记流水, 延时到账
     * @param request
     */
    @RequestSink
    public String delayInTransfer(FundDelayInTransRequest request) {
        return transactionTemplate.execute((TransactionStatus status) -> {

            BigDecimal amount = request.getTransAmount();
            FundsOprType flowType = FundsOprType.DELAY_TRANSFER;
            Date applyDate = request.getApplyDate();

            String outId = request.getOriginAccountId();
            AccountGroup outAccountGroup = request.getOriginAccountGroup();

            AccountSortVo outAccountSortVo = AccountSortVo.getInstance(outId, outAccountGroup);

            // 按字典顺序获取账户信息（悲观锁）
            Map<String, BaseAccount> id2BaseAccount = fundsBalanceBiz.getAccountBySortForUpdate(outAccountSortVo);

            // 出账户资金-
            BaseAccount outAccount = id2BaseAccount.get(outId);
            outAccount = updateOutAccountBalance(amount, outAccount, outAccountGroup);


            /** @notice 进账户资金暂时不更新,只记录流水, 待定时任务噶擦更新余额 */
            String inId = request.getTargetAccountId();
            AccountGroup inAccountGroup = request.getTargetAccountGroup();
            AccountResponseDTO inAccount = accountQueryBiz.getAccount(inId, inAccountGroup);

            // 流水添加
            String flowId = fundsBalanceBiz.addFundsFlow(outId, inId, amount, FundsFlowDirect.OUT,
                    flowType, applyDate, outAccount.getAccountType());
            fundsBalanceBiz.addFundsFlow(inId, outId, amount, FundsFlowDirect.IN,
                    flowType, applyDate, inAccount.getAccountType().getCode());

            return flowId;


        });
    }


    /**
     * 出账户金额更新
     *
     * @param amount          金额
     * @param outAccount      出账户
     * @param outAccountGroup 出账户账户组别
     */
    private BaseAccount updateOutAccountBalance(BigDecimal amount, BaseAccount outAccount, AccountGroup outAccountGroup) {
        // 校验账户状态
        fundsBalanceBiz.checkStatus(outAccount.getStatus(), FundsFlowDirect.OUT);

        BigDecimal outBeginBalance = outAccount.getAvailableBalance();
        // 转账金额校验
        if (amount.compareTo(outBeginBalance) > 0) {
            LoggerUtil.error(log, "账户金额不足, accountId:{}, 现有金额:{}, 转账金额:{}",
                    outAccount.getAccountId(), outBeginBalance, amount);
            throw new AccountException(ErrorCode.BIZ_BALANCE_NOT_ENOUGH);
        }

        // 金额变动
        outAccount.setAvailableBalance(outAccount.getAvailableBalance().subtract(amount));
        outAccount.setTotalBalance(outAccount.getTotalBalance().subtract(amount));
        fundsBalanceBiz.updateBalance(outAccount, outAccountGroup, outBeginBalance);

        return outAccount;
    }


    /**
     * 入账户金额更新
     *
     * @param amount         金额
     * @param inAccount      进账户账户id
     * @param inAccountGroup 进账户账户组别
     */
    private BaseAccount updateInAccountBalance(BigDecimal amount, BaseAccount inAccount, AccountGroup inAccountGroup) {
        // 校验账户状态
        fundsBalanceBiz.checkStatus(inAccount.getStatus(), FundsFlowDirect.IN);

        // 金额变动
        BigDecimal inBeginBalance = inAccount.getAvailableBalance();
        inAccount.setAvailableBalance(inAccount.getAvailableBalance().add(amount));
        inAccount.setTotalBalance(inAccount.getTotalBalance().add(amount));
        fundsBalanceBiz.updateBalance(inAccount, inAccountGroup, inBeginBalance);

        return inAccount;
    }

}
